package downstream

import (
	"context"
	"encoding/base64"
	"fmt"
	"google.golang.org/grpc"
	"google.golang.org/grpc/status"
	"pay/api/pay"
	"pay/api/wxpay"
	"pay/internal/conf"
	"pay/internal/constant"
	"pay/internal/model"
	"pay/pkg/ecode"
	"pay/pkg/log"
	"pay/pkg/network"
	"pay/pkg/request_key"
	"strconv"
	"strings"
	"time"
)

func newWxpayClient(ctx context.Context, addr string) (
	client wxpay.WxpayClient, err error) {
	var conn *grpc.ClientConn
	dialCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*1)
	defer cancel()
	conn, err = grpc.DialContext(dialCtx, addr, grpc.WithInsecure(),
		grpc.WithUnaryInterceptor(network.ClientInterceptor(
			"wxpay", time.Second*3, wxpayErrorTranslator)))
	if err != nil {
		err = ecode.AppendMessage(constant.ErrRemoteError, "wxpay服务地址无法建立有效连接, err=%s", err.Error())
		log.FromContext(ctx).Warnw(err.Error())
		return
	}
	client = wxpay.NewWxpayClient(conn)
	return
}

func wxpayErrorTranslator(err error) (errCode *ecode.ErrorCode) {
	rpcStatus := status.Convert(err)
	errCode = ecode.AppendMessage(
		constant.ErrRemoteError, "wxpay服务调用失败: %s", rpcStatus.Message())
	return
}

func (d *Downstream) UploadImageV2(ctx context.Context, image, imageType string) (
	resp *wxpay.ImageUploadV2Response, err error) {
	if d.wxpayClient == nil {
		if d.wxpayClient, err = newWxpayClient(
			ctx, conf.Conf.Downstream.WxpayAddress); err != nil {
			return
		}
	}
	if strings.Contains(image, "base64,") {
		image = strings.Split(image, "base64,")[1]
	}
	var imageBytes []byte
	if imageBytes, err = base64.StdEncoding.DecodeString(image); err != nil {
		err = ecode.BadRequest("图片格式错误: %s", err.Error())
		return
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*30)
	defer cancel()
	resp, err = d.wxpayClient.ImageUploadV2(clientCtx, &wxpay.ImageUploadV2Request{
		Image:     imageBytes,
		ImageType: imageType,
	})
	return
}

func (d *Downstream) UploadImage(ctx context.Context, mchId, image, imageType string) (
	resp *wxpay.ImageUploadResponse, err error) {
	if d.wxpayClient == nil {
		if d.wxpayClient, err = newWxpayClient(
			ctx, conf.Conf.Downstream.WxpayAddress); err != nil {
			return
		}
	}
	if strings.Contains(image, "base64,") {
		image = strings.Split(image, "base64,")[1]
	}
	var imageBytes []byte
	if imageBytes, err = base64.StdEncoding.DecodeString(image); err != nil {
		err = ecode.BadRequest("图片格式错误: %s", err.Error())
		return
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*30)
	defer cancel()
	resp, err = d.wxpayClient.ImageUpload(clientCtx, &wxpay.ImageUploadRequest{
		Image:     imageBytes,
		ImageType: imageType,
		MchId:     mchId,
	})
	return
}

func (d *Downstream) UploadWechatImage(ctx context.Context, mchId, imageUrl string) (
	resp *wxpay.GetWechatMediaIdReply, err error) {
	if d.wxpayClient == nil {
		if d.wxpayClient, err = newWxpayClient(
			ctx, conf.Conf.Downstream.WxpayAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*30)
	defer cancel()
	resp, err = d.wxpayClient.GetWechatMediaId(clientCtx, &wxpay.GetWechatMediaIdReq{
		ImageUrl: imageUrl,
		MchId:    mchId,
	})
	return
}

func (d *Downstream) WechatApply(ctx context.Context, req *pay.ApplyWechatAccountRequest,
	config *model.WechatConfig, reSubmit bool) (resp *wxpay.ApplymentSubmitResponse, err error) {
	var organizationType string
	switch req.Type {
	case pay.WechatAccountType_WECHAT_ENTERPRISE:
		// 2：企业，营业执照上的主体类型一般为有限公司、有限责任公司。
		organizationType = "2"
	case pay.WechatAccountType_WECHAT_INDEPENDENT:
		// 4：个体工商户，营业执照上的主体类型一般为个体户、个体工商户、个体经营。
		organizationType = "4"
	case pay.WechatAccountType_WECHAT_PLATFORM:
		// 2401：小微商户，指无营业执照的个人商家。
		organizationType = "2401"
	default:
		err = ecode.BadRequest("收款账户类型不合法")
		return
	}
	applyReq := &wxpay.ApplymentSubmitRequest{
		ApplyType:            "ecommerce",
		MchId:                model.BootInfo.WechatServiceProviderMerchantId,
		OrganizationType:     organizationType,
		BizLicenseInfo:       req.BizLicenseInfo,
		OrganizationCertInfo: req.OrganizationCertInfo,
		IdDocType:            req.IdDocType,
		IdCardInfo:           req.IdCardInfo,
		IdDocInfo:            req.IdDocInfo,
		NeedAccountInfo:      req.NeedAccountInfo,
		AccountInfo:          req.AccountInfo,
		ContactInfo:          req.ContactInfo,
		SalesSceneInfo:       req.SalesSceneInfo,
		MerchantShortname:    req.MerchantShortname,
		Qualifications:       req.Qualifications,
		BusinessAdditionPics: req.BusinessAdditionPics,
		BusinessAdditionDesc: req.BusinessAdditionDesc,
		BrandId:              strconv.Itoa(int(req.Token.SgId)),
		BizApplyNo:           fmt.Sprintf("WechatApply%d", config.ID),
	}
	if d.wxpayClient == nil {
		if d.wxpayClient, err = newWxpayClient(
			ctx, conf.Conf.Downstream.WxpayAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*10)
	defer cancel()
	if !reSubmit {
		resp, err = d.wxpayClient.ApplymentSubmit(clientCtx, applyReq)
	} else {
		resp, err = d.wxpayClient.ApplymentReSubmit(clientCtx, applyReq)
	}
	return
}

func (d *Downstream) QueryWechatApply(ctx context.Context, applyNo string, sgId uint32, force bool) (
	resp *wxpay.ApplymentQueryResponse, err error) {
	if d.wxpayClient == nil {
		if d.wxpayClient, err = newWxpayClient(
			ctx, conf.Conf.Downstream.WxpayAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*10)
	defer cancel()
	resp, err = d.wxpayClient.ApplymentQuery(clientCtx, &wxpay.ApplymentQueryRequest{
		BrandId: strconv.Itoa(int(sgId)),
		ApplyNo: applyNo,
		Force:   force,
	})
	return
}

func (d *Downstream) AlipayApply(ctx context.Context, req *pay.ApplyAlipayAccountRequest,
	config *model.AlipayToken, reSubmit bool) (resp *wxpay.AliFaceToFaceSignResponse, err error) {
	applyReq := &wxpay.AliFaceToFaceSignRequest{
		BrandId:                strconv.Itoa(int(req.Token.SgId)),
		BizNo:                  fmt.Sprintf("AlipayApply%d", config.ID),
		AlipayAccount:          config.UserId,
		ContactName:            req.Info.ContactName,
		ContactMobile:          req.Info.ContactMobile,
		ContactEmail:           req.Info.ContactEmail,
		MccCode:                req.Info.MccCode,
		SpecialLicensePic:      req.Info.SpecialLicensePic,
		Rate:                   req.Info.Rate,
		SignAndAuth:            req.Info.SignAndAuth,
		BusinessLicenseNo:      req.Info.BusinessLicenseNo,
		BusinessLicensePic:     req.Info.BusinessLicensePic,
		BusinessLicenseAuthPic: req.Info.BusinessLicenseAuthPic,
		LongTerm:               req.Info.LongTerm,
		DateLimitation:         req.Info.DateLimitation,
		ShopScenePic:           req.Info.ShopScenePic,
		ShopSignBoardPic:       req.Info.ShopSignBoardPic,
		EmpId:                  strconv.Itoa(int(req.Token.EmpId)),
		AuthToken:              config.Token,
		AppId:                  model.BootInfo.AlipayServiceProviderAppId,
	}
	if d.wxpayClient == nil {
		if d.wxpayClient, err = newWxpayClient(
			ctx, conf.Conf.Downstream.WxpayAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*10)
	defer cancel()
	if !reSubmit {
		resp, err = d.wxpayClient.AliPayFaceToFaceSignSubmit(clientCtx, applyReq)
	} else {
		resp, err = d.wxpayClient.AliPayFaceToFaceSignReSubmit(clientCtx, applyReq)
	}
	return
}

func (d *Downstream) QueryAlipayApply(ctx context.Context, config *model.AlipayToken,
	sgId uint32) (resp *wxpay.AliFaceToFaceSignQueryResponse, err error) {
	if d.wxpayClient == nil {
		if d.wxpayClient, err = newWxpayClient(
			ctx, conf.Conf.Downstream.WxpayAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*20)
	defer cancel()
	resp, err = d.wxpayClient.AliPayFaceToFaceSignQuery(clientCtx, &wxpay.AliFaceToFaceSignQueryRequest{
		BrandId:       strconv.Itoa(int(sgId)),
		BizNo:         fmt.Sprintf("AlipayApply%d", config.ID),
		AuthToken:     config.Token,
		AppId:         model.BootInfo.AlipayServiceProviderAppId,
		AlipayAccount: config.UserId,
	})
	return
}

func (d *Downstream) QueryAlipayStatus(ctx context.Context, config *model.AlipayToken) (resp *wxpay.AliSignStatusQueryResponse, err error) {
	if d.wxpayClient == nil {
		if d.wxpayClient, err = newWxpayClient(
			ctx, conf.Conf.Downstream.WxpayAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*10)
	defer cancel()
	resp, err = d.wxpayClient.AliPaySignStatusQuery(clientCtx, &wxpay.AliSignStatusQueryRequest{
		Pid: config.UserId,
		// 当面付产品标识
		ProductCodes: []string{"I1011000100000000001"},
		AuthToken:    config.Token,
		AppId:        model.BootInfo.AlipayServiceProviderAppId,
	})
	return
}

func (d *Downstream) TradeCreate(ctx context.Context, req *wxpay.JSAPIPreCreateRequest) (
	resp *wxpay.JSAPIPreCreateResponse, err error) {
	if d.wxpayClient == nil {
		if d.wxpayClient, err = newWxpayClient(
			ctx, conf.Conf.Downstream.WxpayAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*5)
	defer cancel()
	resp, err = d.wxpayClient.JSAPIPreCreate(clientCtx, req)
	return
}

func (d *Downstream) TradeQuery(ctx context.Context, req *wxpay.TradeQueryRequest) (
	resp *wxpay.TradeQueryResponse, err error) {
	if d.wxpayClient == nil {
		if d.wxpayClient, err = newWxpayClient(
			ctx, conf.Conf.Downstream.WxpayAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*5)
	defer cancel()
	resp, err = d.wxpayClient.TradeQuery(clientCtx, req)
	return
}

func (d *Downstream) TradeClose(ctx context.Context, sgId uint32, payOrderNumber string) (err error) {
	if d.wxpayClient == nil {
		if d.wxpayClient, err = newWxpayClient(
			ctx, conf.Conf.Downstream.WxpayAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*5)
	defer cancel()
	_, err = d.wxpayClient.TradeClose(clientCtx, &wxpay.TradeCloseRequest{
		BrandId:    strconv.Itoa(int(sgId)),
		OutTradeNo: payOrderNumber,
	})
	return
}

func (d *Downstream) TradeRefund(ctx context.Context, req *wxpay.RefundApplyRequest) (
	resp *wxpay.RefundApplyResponse, err error) {
	if d.wxpayClient == nil {
		if d.wxpayClient, err = newWxpayClient(
			ctx, conf.Conf.Downstream.WxpayAddress); err != nil {
			return
		}
	}
	clientCtx, cancel := request_key.NewOutGoingContext(ctx, time.Second*5)
	defer cancel()
	resp, err = d.wxpayClient.RefundApply(clientCtx, req)
	return
}
